#include <sdk.h>
#include <wx/fileconf.h>
#include <vector>
#include "M_Code_StyledTextCtrl.h"
#include "menuutils.h"
#include "cbkeybinder.h"

#pragma implementation "cbkeybinder.h"

wxString* pKeyFilename = 0;

namespace {
  PluginRegistrant<cbKeyBinder> reg( _T( "KeyBinder" ) );
};

BEGIN_EVENT_TABLE( cbKeyBinder, M_Code_Plugin )
  EVT_IDLE( cbKeyBinder::OnIdle )
  EVT_TIMER( -1, cbKeyBinder::OnTimerAlarm )
END_EVENT_TABLE()

BEGIN_EVENT_TABLE( MyDialog, wxPanel )
END_EVENT_TABLE()

cbKeyBinder::cbKeyBinder() : m_Timer( this, 0 ) {
  m_menuPreviouslyBuilt = false;
}

cbKeyBinder::~cbKeyBinder() {
}

void cbKeyBinder::OnAttach() {
  m_menuPreviouslyBuilt = false;
  pcbWindow = Manager::Get()->GetAppWindow();
  m_pKeyProfArr = new wxKeyProfileArray;
  m_bBound = FALSE;
  m_MenuModifiedByMerge = 0;
  m_bTimerAlarm = false;
  m_bAppShutDown = false;
  m_bConfigBusy = false;
  IsMerging( false );
  wxKeyBinder::usableWindows.Add( _( "sciwindow" ) );
  wxKeyBinder::usableWindows.Add( _( "flat notebook" ) );
  m_mergeEnabled = 0;
  m_OldKeyFilename = wxEmptyString;
  Manager::Get()->RegisterEventSink( cbEVT_PROJECT_CLOSE, new cbEventFunctor<cbKeyBinder, CodeBlocksEvent>( this, &cbKeyBinder::OnProjectClosed ) );
  Manager::Get()->RegisterEventSink( cbEVT_EDITOR_OPEN, new cbEventFunctor<cbKeyBinder, CodeBlocksEvent>( this, &cbKeyBinder::OnEditorOpen ) );
  Manager::Get()->RegisterEventSink( cbEVT_EDITOR_CLOSE, new cbEventFunctor<cbKeyBinder, CodeBlocksEvent>( this, &cbKeyBinder::OnEditorClose ) );
  Manager::Get()->RegisterEventSink( cbEVT_PROJECT_OPEN, new cbEventFunctor<cbKeyBinder, CodeBlocksEvent>( this, &cbKeyBinder::OnProjectOpened ) );
  Manager::Get()->RegisterEventSink( cbEVT_APP_STARTUP_DONE, new cbEventFunctor<cbKeyBinder, CodeBlocksEvent>( this, &cbKeyBinder::OnAppStartupDone ) );
  Manager::Get()->RegisterEventSink( cbEVT_APP_START_SHUTDOWN, new cbEventFunctor<cbKeyBinder, CodeBlocksEvent>( this, &cbKeyBinder::OnAppStartShutdown ) );
  Manager::Get()->RegisterEventSink( cbEVT_MENUBAR_CREATE_BEGIN, new cbEventFunctor<cbKeyBinder, CodeBlocksEvent>( this, &cbKeyBinder::OnMenuBarModify ) );
  Manager::Get()->RegisterEventSink( cbEVT_MENUBAR_CREATE_END, new cbEventFunctor<cbKeyBinder, CodeBlocksEvent>( this, &cbKeyBinder::OnMenuBarModify ) );
  return;
}

void cbKeyBinder::OnRelease( bool ) {
  StopMergeTimer();
  if( m_bBound ) {
    EnableMerge( false );
  }
  m_pKeyProfArr->DetachAll();
  delete m_pKeyProfArr;
}

cbConfigurationPanel* cbKeyBinder::GetConfigurationPanel( wxWindow* parent ) {
  if( !IsAttached() ) {
    return 0;
  }
  return OnKeyConfig( parent );
}

void cbKeyBinder::BuildMenu( wxMenuBar* menuBar ) {
  if( !IsAttached() ) {
    return;
  }
  if( m_menuPreviouslyBuilt ) {
    m_pMenuBar = menuBar;
    wxMenuCmd::Register( m_pMenuBar );
    EnableMerge( false );
    for( int i = 0; i < 5; i++ ) {
      if( IsMerging() ) {
        wxSleep( 1 );
      } else
      { break; }
    }
    OnLoad();
    return;
  }
  m_menuPreviouslyBuilt = true;
  m_pMenuBar = menuBar;
  m_sConfigFolder = ConfigManager::GetConfigFolder();
  m_sExecuteFolder = FindAppPath( wxTheApp->argv[0], ::wxGetCwd(), wxEmptyString );
  m_sDataFolder = ConfigManager::GetDataFolder();
  m_sConfigFolder.Replace( _T( "//" ), _T( "/" ) );
  m_sExecuteFolder.Replace( _T( "//" ), _T( "/" ) );
  const PluginInfo* info = Manager::Get()->GetPluginManager()->GetPluginInfo( this );
  wxString m_Personality = Manager::Get()->GetPersonalityManager()->GetPersonality();
  if( m_Personality == wxT( "default" ) ) {
    m_Personality = wxEmptyString;
  }
  m_sKeyFilePath = m_sExecuteFolder;
  m_sKeyFilename = m_sKeyFilePath + _T( '/' );
  if( not m_Personality.IsEmpty() ) {
    m_sKeyFilename << m_Personality + wxT( "." ) ;
  }
  m_sKeyFilename << info->name << _T( ".ini" );
  if( !::wxFileExists( m_sKeyFilename ) ) {
    m_sKeyFilePath = m_sConfigFolder;
    m_sKeyFilename = m_sKeyFilePath + _T( '/' );
    if( not m_Personality.IsEmpty() ) {
      m_sKeyFilename << m_Personality + wxT( "." ) ;
    }
    m_sKeyFilename << info->name  << _T( ".ini" );
  }
  pKeyFilename = &m_sKeyFilename;
  m_bBound = false;
  return ;
}

void cbKeyBinder::BuildModuleMenu( const ModuleType, wxMenu*, const FileTreeData* ) {
  if( !IsAttached() ) {
    return;
  }
  return;
}

bool cbKeyBinder::BuildToolBar( wxToolBar* ) {
  if( !IsAttached() ) {
    return false;
  }
  return false;
}

void cbKeyBinder::Rebind( bool update ) {
  wxKeyProfile *pPrimary;
  pPrimary = new wxKeyProfile( wxT( "Primary" ), wxT( "Our primary keyprofile" ) );
  pPrimary->ImportMenuBarCmd( m_pMenuBar );
  for( int i = 0; i < m_pKeyProfArr->GetCount(); i++ ) {
    m_pKeyProfArr->Remove( m_pKeyProfArr->Item( i ) );
  }
  m_pKeyProfArr->DetachAll();
  m_pKeyProfArr->Cleanup();
  wxMenuCmd::Register( m_pMenuBar );
  m_pKeyProfArr->Add( pPrimary );
  m_pKeyProfArr->SetSelProfile( 0 );
  if( update ) {
    UpdateArr( *m_pKeyProfArr );
  }
  return;
}

void cbKeyBinder::UpdateArr( wxKeyProfileArray &r ) {
  r.DetachAll();
  r.GetSelProfile()->Enable( TRUE );
  r.GetSelProfile()->AttachRecursively( Manager::Get()->GetAppWindow() );
  r.GetSelProfile()->UpdateAllCmd( m_pMenuBar );
  if( VerifyKeyBind( _T( "Ctrl-C" ), 1 ) ) {
    RemoveCopyPasteBindings( r.GetSelProfile() );
  }
}
int cbKeyBinder::EnableMerge( bool allow ) {
  if( not allow ) {
    m_mergeEnabled = 0;
    StopMergeTimer();
    return m_mergeEnabled;
  }
  m_mergeEnabled  = ( m_mergeEnabled < 0 ? 1 : m_mergeEnabled + 1 );
  StartMergeTimer( 15 );
  return m_mergeEnabled;
}

void cbKeyBinder::MergeDynamicMenus() {
  int n;
  if( not( n = IsEnabledMerge() ) ) {
    return;
  }
  EnableMerge( false );
  IsMerging( true );
  m_MenuModifiedByMerge += m_pKeyProfArr->GetSelProfile()->MergeDynamicMenuItems( m_pMenuBar );
  IsMerging( false );
  EnableMerge( true );
  return;
}

cbConfigurationPanel* cbKeyBinder::OnKeyConfig( wxWindow* parent ) {
  if( not m_bBound ) {
    return 0;
  }
  EnableMerge( true );
  MergeDynamicMenus();
  m_bConfigBusy = true;
  EnableMerge( false );
  bool btree = true;
  bool baddprofile = true;
  bool bprofiles = true;
  bool bprofileedit = true;
  int mode = btree ? wxKEYBINDER_USE_TREECTRL : wxKEYBINDER_USE_LISTBOX;
  if( baddprofile ) {
    mode |= wxKEYBINDER_SHOW_ADDREMOVE_PROFILE;
  }
  if( bprofileedit ) {
    mode |= wxKEYBINDER_ENABLE_PROFILE_EDITING;
  }
  MyDialog* dlg = new MyDialog( this, *m_pKeyProfArr, parent, _T( "Keybindings" ), mode );
  dlg->m_p->EnableKeyProfiles( bprofiles );
  return dlg;
}

void cbKeyBinder::OnKeyConfigDialogDone( MyDialog* dlg ) {
  EnableMerge( false );
  dlg->m_p->ApplyChanges();
  *m_pKeyProfArr = dlg->m_p->GetProfiles();
  UpdateArr( *m_pKeyProfArr ) ;
  bool backUpCfg = true;
  OnSave( backUpCfg = true );
  m_MenuModifiedByMerge = 0;
  m_pKeyProfArr->GetSelProfileIdx();
}

void cbKeyBinder::OnLoad() {
  EnableMerge( false );
  if( not m_OldKeyFilename.IsEmpty() ) {
    wxString oldKeyFile = m_sKeyFilePath + _T( '/' ) + m_OldKeyFilename;
    if( not ::wxFileExists( m_sKeyFilename ) )
      if( ::wxFileExists( oldKeyFile ) ) {
        ::wxCopyFile( oldKeyFile, m_sKeyFilename );
      }
  }
  m_bBound = TRUE;
  m_pKeyProfArr->DetachAll();
  m_pKeyProfArr->Cleanup();
  wxMenuCmd::Register( m_pMenuBar );
  wxString strLoadFilename = m_sKeyFilename;
  wxFileConfig cfg( wxEmptyString, wxEmptyString, strLoadFilename, wxEmptyString, wxCONFIG_USE_LOCAL_FILE );
  if( m_pKeyProfArr->Load( &cfg, wxEmptyString ) ) {
    int total = 0;
    for( int i = 0; i < m_pKeyProfArr->GetCount(); i++ ) {
      total += m_pKeyProfArr->Item( i )->GetCmdCount();
    }
    if( total == 0 ) {
      wxString msg;
      msg << wxT( "KeyBinder: No keyprofiles have been found...\n" )
          << strLoadFilename.c_str()
          << wxT( "\nmay be corrupted.\n" )
          << wxT( "A default keyprofile will be set." );
      wxMessageBox( msg, wxT( "KeyBinder" ) );
      Rebind();
    }
    MergeAcceleratorTable( false );
    UpdateArr( *m_pKeyProfArr );
  } else {
    Rebind( false );
    MergeAcceleratorTable( true );
    UpdateArr( *m_pKeyProfArr );
  }
  if( not IsEnabledMerge() ) {
    EnableMerge( true );
  }
  return;
}

void cbKeyBinder::OnSave( bool backitup ) {
  ::wxRemoveFile( m_sKeyFilename );
  wxString path = m_sKeyFilename;
  wxFileConfig *cfg = new wxFileConfig( wxEmptyString, wxEmptyString, path );
  if( m_pKeyProfArr->Save( cfg, wxEmptyString, TRUE ) ) {
    int total = 0;
    for( int i = 0; i < m_pKeyProfArr->GetCount(); i++ ) {
      total += m_pKeyProfArr->Item( i )->GetCmdCount();
    }
    cfg->Flush();
    if( backitup && ::wxFileExists( m_sKeyFilename ) ) {
      ::wxCopyFile( m_sKeyFilename, m_sKeyFilename + _T( ".bak" ) );
    }
  } else {
    wxMessageBox( wxT( "Keybinder:Error saving key file!" ), wxT( "Save Error" ), wxOK | wxICON_ERROR );
  }
  delete cfg;
}

void cbKeyBinder::MergeAcceleratorTable( const bool mergeAccelTable ) {
  wxUnusedVar( mergeAccelTable );
  return;
}

int cbKeyBinder::RemoveKeyBindingsFor( const wxString& strKeyCode, wxKeyProfile* pkp ) {
  int knt = 0;
  wxCmd* pwxcmd = 0;
  do {
    pwxcmd = pkp->GetCmdBindTo( strKeyCode );
    if( pwxcmd ) {
      ++knt;
      pkp->RemoveCmd( pwxcmd );
    }
  } while( pwxcmd );
  return knt;
}

int cbKeyBinder::RemoveCopyPasteBindings( wxKeyProfile* pkp ) {
  wxCmd* pwxcmd = 0;
  int knt = 0;
  pwxcmd = pkp->GetCmdBindTo( _T( "Ctrl-C" ) );
  if( pwxcmd && ( pwxcmd->GetName() == _T( "Copy" ) ) ) {
    knt +=  RemoveKeyBindingsFor( _T( "Ctrl-C" ), pkp );
  }
  pwxcmd = pkp->GetCmdBindTo( _T( "Ctrl-V" ) );
  if( pwxcmd && ( pwxcmd->GetName() == _T( "Paste" ) ) ) {
    knt +=  RemoveKeyBindingsFor( _T( "Ctrl-V" ), pkp );
  }
  pwxcmd = pkp->GetCmdBindTo( _T( "Ctrl-X" ) );
  if( pwxcmd && ( pwxcmd->GetName() == _T( "Cut" ) ) ) {
    knt +=  RemoveKeyBindingsFor( _T( "Ctrl-X" ), pkp );
  }
  return knt;
}

bool cbKeyBinder::VerifyKeyBind( const wxString& strKeyCode, const int numShortcuts ) {
  wxKeyProfile* pkp = m_pKeyProfArr->GetSelProfile();
  bool kbOk = true;
  wxCmd* pcmd = pkp->GetCmdBindTo( strKeyCode );
  if( pcmd ) {
    int shortcutsCount = pcmd->GetShortcutCount();
    wxString desc = pcmd->GetDescription();
    wxString name = pcmd->GetName();
    if( shortcutsCount not_eq numShortcuts ) {
      kbOk = false;
    }
    for( int kk = 0; kk < shortcutsCount; ++kk ) {
      wxKeyBind* pkbind = pcmd->GetShortcut( kk );
      if( pkbind ) {
        wxString strKeyCodeLoc = wxKeyBind::KeyCodeToString( pkbind->GetKeyCode() );
        if( pkbind->GetModifiers() & wxACCEL_SHIFT ) {
          strKeyCodeLoc.Prepend( _T( "Shift-" ) );
        }
        if( pkbind->GetModifiers() & wxACCEL_CTRL ) {
          strKeyCodeLoc.Prepend( _T( "Ctrl-" ) );
        }
        if( pkbind->GetModifiers() & wxACCEL_ALT ) {
          strKeyCodeLoc.Prepend( _T( "Alt-" ) );
        }
      } else
      { kbOk = false; }
    }
  } else {
    kbOk = false;
  }
  return kbOk;
}

MyDialog::MyDialog( cbKeyBinder* binder, wxKeyProfileArray &prof, wxWindow *parent, const wxString &, int mode ) : m_pBinder( binder ) {
  cbConfigurationPanel::Create( parent, -1, wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER );
  m_p = new wxKeyConfigPanel( this, mode );
  m_p->AddProfiles( prof );
  m_p->ImportMenuBarCmd( ( ( wxFrame* )Manager::Get()->GetAppWindow() )->GetMenuBar() );
  wxBoxSizer *main = new wxBoxSizer( wxVERTICAL );
  main->Add( m_p, 1, wxGROW );
  SetSizer( main );
  main->SetSizeHints( this );
}

MyDialog::~MyDialog() {
  m_pBinder->EnableMerge( true );
  m_pBinder->m_bConfigBusy = false;
}

void MyDialog::OnApply() {
  m_pBinder->OnKeyConfigDialogDone( this );
}

void cbKeyBinder::OnProjectOpened( CodeBlocksEvent& event ) {
  event.Skip();
}

void cbKeyBinder::OnProjectActivated( CodeBlocksEvent& event ) {
  event.Skip();
}

void cbKeyBinder::OnProjectClosed( CodeBlocksEvent& event ) {
  if( IsAttached() ) {
    m_EditorPtrs.Shrink();
  }
  event.Skip();
}

void cbKeyBinder::OnProjectFileAdded( CodeBlocksEvent& event ) {
  event.Skip();
}

void cbKeyBinder::OnProjectFileRemoved( CodeBlocksEvent& event ) {
  event.Skip();
}

void cbKeyBinder::AttachEditor( wxWindow* pWindow ) {
  if( IsAttached() ) {
    wxWindow* thisEditor = pWindow->FindWindowByName( _T( "SCIwindow" ), pWindow );
    if( thisEditor && ( wxNOT_FOUND == m_EditorPtrs.Index( thisEditor ) ) ) {
      m_EditorPtrs.Add( thisEditor );
      m_pKeyProfArr->GetSelProfile()->Attach( thisEditor );
    }
  }
}

void cbKeyBinder::DetachEditor( wxWindow* pWindow, bool deleteEvtHandler ) {
  if( IsAttached() ) {
    wxWindow* thisWindow = pWindow;
    wxWindow* thisEditor = thisWindow->FindWindowByName( _T( "SCIwindow" ), thisWindow );
    if( thisEditor && ( m_EditorPtrs.Index( thisEditor ) != wxNOT_FOUND ) ) {
      m_pKeyProfArr->GetSelProfile()->Detach( thisEditor, deleteEvtHandler );
      m_EditorPtrs.Remove( thisEditor );
    }
  }
}

void cbKeyBinder::OnEditorOpen( CodeBlocksEvent& event ) {
  if( IsAttached() ) {
    if( not m_bBound ) {
      OnAppStartupDone( event );
    }
    wxWindow* thisWindow = event.GetEditor();
    wxWindow* thisEditor = thisWindow->FindWindowByName( _T( "SCIwindow" ), thisWindow );
    M_Code_Editor* ed = 0;
    EditorBase* eb = event.GetEditor();
    if( eb && eb->IsBuiltinEditor() ) {
      ed = static_cast<M_Code_Editor*>( eb );
      thisEditor = ed->GetControl();
    }
    if( thisEditor && ( wxNOT_FOUND == m_EditorPtrs.Index( thisEditor ) ) ) {
      m_EditorPtrs.Add( thisEditor );
      m_pKeyProfArr->GetSelProfile()->Attach( thisEditor );
    }
  }
  event.Skip();
}

void cbKeyBinder::OnEditorClose( CodeBlocksEvent& event ) {
  if( IsAttached() && m_bBound ) {
    wxWindow* thisWindow = event.GetEditor();
    wxWindow*
    thisEditor = thisWindow->FindWindowByName( _T( "SCIwindow" ), thisWindow );
    M_Code_Editor* ed = 0;
    EditorBase* eb = event.GetEditor();
    if( eb && eb->IsBuiltinEditor() ) {
      ed = static_cast<M_Code_Editor*>( eb );
      thisEditor = ed->GetControl();
    }
    if( thisEditor && ( m_EditorPtrs.Index( thisEditor ) != wxNOT_FOUND ) ) {
      m_pKeyProfArr->GetSelProfile()->Detach( thisEditor );
      m_EditorPtrs.Remove( thisEditor );
    }
  }
  event.Skip();
}

void cbKeyBinder::OnAppStartupDone( CodeBlocksEvent& event ) {
  if( not m_bBound ) {
    OnLoad();
  }
  Connect( wxEVT_CREATE, ( wxObjectEventFunction )( wxEventFunction )
           ( wxCommandEventFunction ) &cbKeyBinder::OnWindowCreateEvent );
  Connect( wxEVT_DESTROY, ( wxObjectEventFunction )( wxEventFunction )
           ( wxCommandEventFunction ) &cbKeyBinder::OnWindowDestroyEvent );
  event.Skip();
  return;
}

void cbKeyBinder::OnWindowCreateEvent( wxEvent& event ) {
  if( m_bBound ) {
    wxWindow* pWindow = ( wxWindow* )( event.GetEventObject() );
    M_Code_Editor* ed = 0;
    M_Code_StyledTextCtrl* pRightSplitWin = 0;
    ed  = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
    if( ed ) {
      pRightSplitWin = ed->GetRightSplitViewControl();
      if( pWindow && ( pRightSplitWin == 0 ) ) {
        if( pWindow->GetParent() == ed ) {
          AttachEditor( pWindow );
        }
      }
    }
  }
  event.Skip();
}

void cbKeyBinder::OnWindowDestroyEvent( wxEvent& event ) {
  if( not m_bBound ) {
    event.Skip();
    return;
  }
  wxWindow* thisWindow = ( wxWindow* )( event.GetEventObject() );
  if( ( thisWindow ) && ( m_EditorPtrs.Index( thisWindow ) != wxNOT_FOUND ) ) {
    DetachEditor( thisWindow, false );
  }
  event.Skip();
}

void cbKeyBinder::OnTimerAlarm( wxTimerEvent& ) {
  m_bTimerAlarm = true;
}

void cbKeyBinder::OnIdle( wxIdleEvent& event ) {
  if( not m_bTimerAlarm ) {
    event.Skip();
    return;
  }
  if( m_bAppShutDown ) {
    event.Skip();
    return;
  }
  if( not m_bBound ) {
    event.Skip();
    return;
  }
  if( IsMerging() ) {
    event.Skip();
    return;
  }
  if( m_bConfigBusy ) {
    event.Skip();
    return;
  }
  if( IsEnabledMerge() ) {
    MergeDynamicMenus();
    if( m_MenuModifiedByMerge ) {
      EnableMerge( false );
      if( not m_bAppShutDown ) {
        OnSave();
      }
      m_MenuModifiedByMerge = 0;
    }
    m_bTimerAlarm = false;
    if( not m_bAppShutDown ) {
      EnableMerge( true );
    }
  }
  event.Skip();
}

void cbKeyBinder::OnAppStartShutdown( CodeBlocksEvent& event ) {
  m_bAppShutDown = true;
  EnableMerge( false );
  m_bTimerAlarm = false;
  for( int i = 0; i < 5 ; ++i ) {
    if( IsMerging() ) {
      ::wxSleep( 1 );
      wxYield();
    } else
    { break; }
  }
  EnableMerge( false );
  event.Skip();
}

void cbKeyBinder::OnMenuBarModify( CodeBlocksEvent& event ) {
  wxString sEventType;
  int modType = event.GetEventType();
  if( modType == cbEVT_MENUBAR_CREATE_BEGIN ) {
    sEventType = wxT( "BEGIN" );
  }
  if( modType == cbEVT_MENUBAR_CREATE_END ) {
    sEventType = wxT( "END" );
  }
  if( modType == cbEVT_MENUBAR_CREATE_BEGIN ) {
    for( int i = 0; i < 5 ; ++i ) {
      if( IsMerging() ) {
        ::wxSleep( 1 );
        wxYield();
      } else
      { break; }
    }
    EnableMerge( false );
  }
  if( modType == cbEVT_MENUBAR_CREATE_END ) {
    OnLoad();
  }
  event.Skip();
}

wxString cbKeyBinder::FindAppPath( const wxString& argv0, const wxString& cwd, const wxString& appVariableName ) {
  wxString str;
  if( !appVariableName.IsEmpty() ) {
    str = wxGetenv( appVariableName );
    if( !str.IsEmpty() ) {
      return str;
    }
  }
  wxString argv0Str = argv0;
  if( wxIsAbsolutePath( argv0Str ) ) {
    return wxPathOnly( argv0Str );
  } else {
    wxString currentDir( cwd );
    if( currentDir.Last() != wxFILE_SEP_PATH ) {
      currentDir += wxFILE_SEP_PATH;
    }
    str = currentDir + argv0Str;
    if( wxFileExists( str ) ) {
      return wxPathOnly( str );
    }
  }
  wxPathList pathList;
  pathList.AddEnvList( wxT( "PATH" ) );
  str = pathList.FindAbsoluteValidPath( argv0Str );
  if( !str.IsEmpty() ) {
    return wxPathOnly( str );
  }
  return wxEmptyString;
}
